package com.ruicar.afs.cloud.admin.service.impl;

import cn.hutool.core.util.ArrayUtil;
import cn.hutool.crypto.SecureUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruicar.afs.cloud.admin.api.dto.UserSimpleInfoDto;
import com.ruicar.afs.cloud.admin.entity.*;
import com.ruicar.afs.cloud.admin.enums.SysParamType;
import com.ruicar.afs.cloud.admin.mapper.SysUserMapper;
import com.ruicar.afs.cloud.admin.service.*;
import com.ruicar.afs.cloud.admin.condition.UserCondition;
import com.ruicar.afs.cloud.common.core.constant.CommonConstants;
import com.ruicar.afs.cloud.common.core.enums.AfsEnumUtil;
import com.ruicar.afs.cloud.common.core.exception.AfsBaseException;
import com.ruicar.afs.cloud.common.core.query.QueryCondition;
import com.ruicar.afs.cloud.common.core.security.dto.LoginLogDto;
import com.ruicar.afs.cloud.common.core.security.dto.UserInfo;
import com.ruicar.afs.cloud.common.core.security.feign.Oauth2OperationService;
import com.ruicar.afs.cloud.common.core.security.service.SubAuthService;
import com.ruicar.afs.cloud.common.core.security.util.SecurityUtils;
import com.ruicar.afs.cloud.common.core.security.vo.RoleVo;
import com.ruicar.afs.cloud.components.datadicsync.SysParamHelper;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

import static com.ruicar.afs.cloud.common.core.constant.SecurityConstants.PASSWORD_ENCODE_TYPE;

/**
 * @author Fzero
 * @date 2017/10/31
 */
@Slf4j
@Service
@AllArgsConstructor
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements SysUserService {
	private final SysRoleService sysRoleService;
	private final SysPermissionService sysPermissionService;
	private final SysRoleDataScopeService sysRoleDataScopeService;
	private final List<SubAuthService> subAuthServices;
	private final SysUserPassHisService sysUserPassHisService;
	private final SysUserRoleService userRoleService;
	private final Oauth2OperationService oauth2OperationService;
	private static final long DEFAULT_PASS_CHANGE_TIME=30;
	/**
	 * 通过查用户的全部信息
	 *
	 * @param sysUser 用户
	 * @return
	 */
	@Override
	public UserInfo findUserInfo(SysUser sysUser) {
		UserInfo userInfo = new UserInfo();
		userInfo.setLoginLogDto(new LoginLogDto());
		userInfo.getLoginLogDto().setWaitTime(SysParamHelper.getLong(AfsEnumUtil.key(SysParamType.MAX_PASS_ERROR_LOCK_TIME),30)*CommonConstants.MIN_TIME_UNIT);
		if(sysUser.getPassErrorCount()>=SysParamHelper.getLong(AfsEnumUtil.key(SysParamType.MAX_PASS_ERROR_COUNT),3)){
			if((System.currentTimeMillis()-sysUser.getLastLoginFailTime())<SysParamHelper.getLong(AfsEnumUtil.key(SysParamType.MAX_PASS_ERROR_LOCK_TIME),30)*CommonConstants.MIN_TIME_UNIT){
				userInfo.getLoginLogDto().setLastFailTime(sysUser.getLastLoginFailTime());
				userInfo.getLoginLogDto().setMaxFailCount(sysUser.getPassErrorCount());
				userInfo.getLoginLogDto().setForbidden(true);
				return userInfo;
			}
		}
		userInfo.getLoginLogDto().setMaxFailCount(sysUser.getPassErrorCount());
		userInfo.getLoginLogDto().setCanTryCount((int)SysParamHelper.getLong(AfsEnumUtil.key(SysParamType.MAX_PASS_ERROR_COUNT),3));
		userInfo.getLoginLogDto().setForbidden(false);
		if(sysUser.getAutoLogOutTime()==null||sysUser.getAutoLogOutTime()<0){
			sysUser.setAutoLogOutTime(Integer.valueOf(SysParamHelper.getString(AfsEnumUtil.key(SysParamType.PC_AUTO_LOG_OUT_TIME),"30")));
		}
		userInfo.setSysUser(sysUser.transToUserVo());
		List<SysRole> roleList = sysRoleService.findRolesByUserId(sysUser.getUserId());
		//设置角色列表  （ID）
		List<Long> roleIds = roleList
				.stream()
				.map(SysRole::getId)
				.collect(Collectors.toList());
		userInfo.setRoles(ArrayUtil.toArray(roleIds, Long.class));
		userInfo.setRoleList(roleList.stream().map(SysRole::transToRoleVo).collect(Collectors.toList()));
		//设置权限列表（sysPermission.permissions）
		Set<String> permissions = new HashSet<>();
		List<String> permissionList = sysPermissionService.findByUserId(sysUser.getUserId())
				.stream()
				.filter(sysPermission -> StringUtils.isNotEmpty(sysPermission.getPermissions()))
				.map(SysPermission::getPermissions)
				.collect(Collectors.toList());
		permissions.addAll(permissionList);
		userInfo.setPermissions(ArrayUtil.toArray(permissions, String.class));
		List<Long> dataScopes = new ArrayList<>();
		Arrays.stream(userInfo.getRoles()).forEach(roleId->{
			dataScopes.addAll(sysRoleDataScopeService
					.list(Wrappers.<SysRoleDataScope>lambdaQuery().eq(SysRoleDataScope::getRoleId,roleId))
					.stream().map(sysRoleDataScope -> sysRoleDataScope.getDeptId()).collect(Collectors.toList()));
		});
		userInfo.setDataScope(dataScopes.stream().collect(Collectors.toSet()));
		userInfo.setUserExtInfo(new JSONObject());
		//设置自定义权限数据
		processSubAuth(userInfo);
		if((System.currentTimeMillis()-sysUser.getLastPassChangeTime())>(SysParamHelper.getLong(AfsEnumUtil.key(SysParamType.PASS_CHANGE_CYCLE_TIME),DEFAULT_PASS_CHANGE_TIME)*CommonConstants.DAY_TIME_UNIT)){
			userInfo.setNeedChangePass(true);
		}else{
			userInfo.setNeedChangePass(false);
		}
		return userInfo;
	}

	@Override
	@CacheEvict(value = CommonConstants.USER_CACHE_KEY_PREFIX, key = "#u.username")
	public Boolean modifyPass(UserCondition userDto, User u) {
		SysUser old = this.getById(SecurityUtils.getUser().getId());
		old.setPassword(SysUserService.ENCODER.encode(userDto.getNewPass()));
		if(!SysParamHelper.getBoolean(AfsEnumUtil.key(SysParamType.HIS_PASS_ENABLE),false)){
			int count = sysUserPassHisService.lambdaQuery()
					.eq(SysUserPassHis::getUserName,old.getUsername())
					.eq(SysUserPassHis::getTenantId,old.getTenantId())
					.eq(SysUserPassHis::getHisPassword, SecureUtil.md5(userDto.getNewPass())).count();
			if(count>0){
				throw new AfsBaseException(CommonConstants.FAIL,"密码不能和历史使用过密码相同");
			}
		}
		SysUserPassHis sysUserPassHis = SysUserPassHis
					.builder()
					.logTimestamp(System.currentTimeMillis())
					.hisPassword(SecureUtil.md5(userDto.getNewPass()))
					.userName(old.getUsername())
					.tenantId(old.getTenantId())
					.build();
		sysUserPassHisService.save(sysUserPassHis);
		old.setLastPassChangeTime(System.currentTimeMillis());
		old.setPassErrorCount(0);
		return this.updateById(old);
	}

	@Override
	@CacheEvict(value = CommonConstants.USER_CACHE_KEY_PREFIX, key = "#userName")
	@Transactional(rollbackFor = Exception.class)
	public void loginFail(String userName) {
		SysUser sysUser =  this.getOne(Wrappers.<SysUser>query()
				.lambda().eq(SysUser::getUsername, userName));
		if(sysUser==null){
			log.info("{}不存在",userName);
			return;
		}
		if(sysUser.getPassErrorCount()<SysParamHelper.getLong(AfsEnumUtil.key(SysParamType.MAX_PASS_ERROR_COUNT),3)) {
			sysUser.setPassErrorCount(sysUser.getPassErrorCount() + 1);
			sysUser.setLastLoginFailTime(System.currentTimeMillis());
			this.updateById(sysUser);
		}
	}

	@Override
	@CacheEvict(value = CommonConstants.USER_CACHE_KEY_PREFIX, key = "#user.username")
	@Transactional(rollbackFor = Exception.class)
	public void resetLoginFailTime(SysUser user) {
		if(user==null){
			log.info("用户不存在");
			return;
		}
		user.setPassErrorCount(0);
		user.setLastLoginFailTime(0L);
		this.updateById(user);
	}

	@Override
	@CacheEvict(value = CommonConstants.USER_CACHE_KEY_PREFIX, key = "#userName")
	@Transactional(rollbackFor = Exception.class)
	public void resetLoginFailTime(String userName) {
		SysUser sysUser =  this.getOne(Wrappers.<SysUser>query()
				.lambda().eq(SysUser::getUsername, userName));
		if(sysUser==null){
			log.info("{}不存在",userName);
			return;
		}
		sysUser.setPassErrorCount(0);
		sysUser.setLastLoginFailTime(0L);
		this.updateById(sysUser);
	}


	@Override
	public List<UserSimpleInfoDto> querySimpleInfosByNames(List<String> userNames) {
		return this.baseMapper.querySimpleInfoByNames(userNames);
	}

	@Override
	public Page<UserSimpleInfoDto> pageQuerySimpleUserInfo(QueryCondition<UserSimpleInfoDto> condition) {
		return this.baseMapper.pageQuerySimpleUserInfo(condition.page(),condition.getCondition().getUserRealName(),condition.getCondition().getDeptName());
	}

	@Override
	public List<UserSimpleInfoDto> getSimpleUserListByRoleCode(String roleCode) {
		return this.baseMapper.querySimpleUserListByRoleCode(roleCode);
	}

	@Override
	public boolean validatePassWord(String passWord) {
		User u = SecurityUtils.getUser();
		return ENCODER.matches(passWord, u.getPassword().substring(PASSWORD_ENCODE_TYPE.length()));
	}


	private void processSubAuth(UserInfo userInfo){
		Set<String> permissions = new HashSet<>();
		permissions.addAll(Arrays.asList(userInfo.getPermissions()));
		List<Long> dataScopes = new ArrayList<>();
		dataScopes.addAll(userInfo.getDataScope());
		JSONObject userExtInfo = userInfo.getUserExtInfo()==null?(new JSONObject()):userInfo.getUserExtInfo();
		List<RoleVo> roleList = new ArrayList<>();
		roleList.addAll(userInfo.getRoleList());
		subAuthServices.forEach(subAuthService -> {
			permissions.addAll(subAuthService.permissions(userInfo.getSysUser().getUsername(),userInfo.getSysUser().getTenantId()));
			dataScopes.addAll(subAuthService.dataScope(userInfo.getSysUser().getUsername(),userInfo.getSysUser().getTenantId()));
			roleList.addAll(subAuthService.extSubRoles(userInfo.getSysUser().getUsername(),userInfo.getSysUser().getTenantId()));
			userExtInfo.putAll(subAuthService.getExtUserInfo(userInfo.getSysUser().getUsername(),userInfo.getSysUser().getTenantId()));
		});

		userInfo.setPermissions(ArrayUtil.toArray(permissions.stream().distinct().collect(Collectors.toList()),String.class));
		userInfo.setDataScope(dataScopes.stream().collect(Collectors.toSet()));
		userInfo.setRoleList(roleList.stream().distinct().collect(Collectors.toList()));
		userInfo.setUserExtInfo(userExtInfo);
	}

	@Override
	@Transactional
	@CacheEvict(value = CommonConstants.USER_CACHE_KEY_PREFIX,allEntries=true)
	public void deleteByUserIds(List<Long> userIds) {
		this.removeByIds(userIds);
		for (Long id : userIds) {
			//删除关联角色
			userRoleService.deleteByUserId(id);
		}
	}

	@Override
	@CacheEvict(value = CommonConstants.USER_CACHE_KEY_PREFIX, key = "#sysUser.username")
	public void updateUser(SysUser sysUser) {
		this.updateById(sysUser);
	}

	@Override
	@CacheEvict(value = CommonConstants.USER_CACHE_KEY_PREFIX, key = "#sysUser.username")
	public void kickOutUser(SysUser sysUser) {
		try{
			oauth2OperationService.kickOut(sysUser.getUsername());
		}catch (Exception e){
			log.error("踢出用户失败",e);
		}
	}
}
